{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 01. Introduction\n",
    "\n",
    "通过从零开始编写一个 Redis 服务器，学习如何构建真实世界中的软件。如果你能构建一个 Redis 服务器，你几乎就能构建任何软件！因为这会教会你两项基础技能：\n",
    "\n",
    "- 网络编程。编程的下一个阶段是为多台机器编程。想想 HTTP 服务器、RPC、数据库、分布式系统。\n",
    "- 数据结构。人们常说它们没用，只在 LeetCode 的题目里出现。Redis 是最佳的反例。基础数据结构有很多有用的应用，但你需要了解它们，才能学以致用。\n",
    "\n",
    "什么是 Redis？\n",
    " Redis 是最流行的内存键值存储，主要用于缓存，因为没有任何存储比内存更快。缓存服务器不可避免，因为它是最简单的扩展方式。缓存可以把缓慢的数据库查询从系统中剔除。\n",
    "\n",
    "缓存服务器就是网络上的一个 `map<string, string>`。但在 Redis 中，“value” 部分不仅仅是一个字符串，它可以是任意数据结构：哈希、列表或有序集合，从而支持复杂的用例，比如排行榜、列表分页等。这就是 Redis 被称为数据结构服务器的原因。\n",
    "\n",
    "为什么要从零开始？\n",
    " 引用费曼的话：“我不能创造的东西，我就无法理解。” “创造”和“理解”有不同的层次。拼接库也是一种“创造”，但你不必停留在那一层——你可以去创造更底层的东西，并理解更基础的原理。\n",
    "\n",
    "目标不是重新发明轮子，而是打牢基础，这会让你脱颖而出。你会做出更好的决策，更擅长调试，并拥有更多的职业选项。\n",
    "\n",
    "为什么选择 C/C++？\n",
    " Redis 的第一个版本是用 TCL 脚本语言写的。但那只是一个玩具版的 Redis；最终的产品是用 C 写成的。打造某样东西的玩具版本是理解它的好方法。但总有更多东西值得学习：\n",
    "\n",
    "- 高性能软件需要低层控制，而这就需要 C/C++。\n",
    "- C 没有内置数据结构或网络功能，它迫使你去学习。\n",
    "- 代码是纯 C，只有极少数可选的 C++ 特性。即使没有 C/C++ 经验，也应该能读懂。我希望这能激励你去学习 C/C++。不过，其他语言在部分课程中也同样适用。\n",
    "\n",
    "Go 更高层，但在编写数据结构，以及处理比特和字节时依然很好。它有成熟的内置网络库，因此你不会学到完整的网络课程。\n",
    " Python 对于编写诸如哈希表一类的数据结构来说太高层了，因为它是内置的。生产环境中的东西多数是 C 加上 Python 封装。Python 只有一个很薄的 socket 封装，所以网络课程仍然适用。\n",
    " JavaScript 和 Python 一样高层，但有一个看不见的事件循环和内置的网络功能。要彻底理解 JS 或 Go，你需要了解超过 JS 或 Go 本身的知识。\n",
    "\n",
    "为什么要编写一个 Redis？\n",
    "\n",
    "- 它能让你为更技术化、系统化的工作做好准备，并在通用软件开发被 AI 工具贬值的时代，为你带来新的职业选择。\n",
    "- 它是很好的实践，尤其是当你是没有真实项目经验的学生时。\n",
    "- 它是对基础知识的快速复习，对求职面试很有帮助。\n",
    "- 了解事物如何运作，本身就很有趣、很能激发智力。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 02. Socket Programming"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.1 要学习什么：从黑盒到代码\n",
    "\n",
    "计算机网络常被简化为通过线连接的盒子，但真正的编码部分却被忽略了。然而，网络编程并不简单。假设有一个包含 `send data` 和 `receive data` 两个方法的 API，你还需要了解什么？\n",
    "\n",
    "**TCP 字节流与协议**\n",
    " 人们常把计算机网络形象化为同伴之间交换“消息”。但最常见的协议 TCP 实际上并不会生成消息，它产生的是没有内部边界的连续字节流。解释这个字节流是应用层协议的工作——它制定规则，让我们理解字节流，并将其拆分成消息。\n",
    "\n",
    "在事件循环内将字节流拆分成消息比你想象的更棘手，它和解析某些文件格式不一样。\n",
    "\n",
    "**数据序列化**\n",
    " 你想通过网络发送的“消息”可能是字符串、结构体、列表等高级对象。然而，计算机网络只认识 0 和 1。因此必须创建对象与字节之间的映射。这被称为序列化（对象转字节）和反序列化（字节转对象）。\n",
    "\n",
    "虽然可以直接使用 JSON 或 Protobuf 等库，但通过手动操作比特和字节来实现序列化，是开展底层编程的良好起点。\n",
    "\n",
    "**并发编程**\n",
    " 有了协议规范，你可以很容易地构建客户端应用。但服务器端更复杂，因为它要处理多个连接。处理大量并发连接（即使大多是空闲的）历来很困难（C10K 问题）。虽然现在的硬件上 10K 只是个小数字，但仍需要高效的软件才能充分利用硬件。\n",
    "\n",
    "现代软件的解决方案是以事件循环为核心的事件驱动并发，这也是 NGINX、Redis、Golang 运行时等现代可扩展软件的基础。事件驱动并发可能对你来说是一种新的编程范式，而且相当复杂，所以必须动手实践才能学会。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.2 从程序员视角看网络\n",
    "\n",
    " **协议栈分层**\n",
    " 抽象概念：网络协议被划分为若干层。低层可以将高层作为载荷，高层则在此基础上增加新的功能。\n",
    "\n",
    "现实中：以太网承载 IP，IP 承载 UDP 或 TCP，UDP 或 TCP 承载应用层协议。\n",
    "\n",
    "我们也可以按功能来划分层次：\n",
    "\n",
    "**小而离散的消息层（IP）**\n",
    " 当你下载大文件时，硬件不可能在转发前存储完整数据，它只能处理较小的单位（IP 数据包）。因此最低层是基于包的。将数据包组装成应用数据的能力由更高层提供，通常是 TCP。\n",
    "\n",
    "**复用层（端口号）**\n",
    " 多个应用可以在一台计算机上共享同一个网络。计算机如何知道某个数据包属于哪个应用？这叫做解复用。IP 的下一层（UDP 或 TCP）通过添加 16 位端口号来区分不同应用。每个应用在发送或接收数据前，都必须占用一个未使用的本地端口号。计算机通过四元组来识别一条“流”：\n",
    "\n",
    "\n",
    "```elm\n",
    "(src_ip, src_port, dst_ip, dst_port)\n",
    "```\n",
    "\n",
    "**可靠有序字节层（TCP）**\n",
    " 小消息通常不符合需求，比如文件传输需要任意大的数据。更糟糕的是，网络不可靠，IP 包可能丢失或乱序。TCP 在 IP 包之上提供可靠、有序的字节流，自动处理重传、重排等问题。\n",
    "\n",
    "**TCP/IP 模型**\n",
    " 按功能划分的网络协议层：\n",
    "\n",
    "| 层次 | 功能                         |\n",
    "| ---- | ---------------------------- |\n",
    "| 高层 | TCP：可靠、有序字节流        |\n",
    "| ↕    | TCP/UDP 中的端口：复用到程序 |\n",
    "| 低层 | IP：小而离散的消息           |\n",
    "\n",
    "三层分别代表网络中的三种需求。它们很好地映射到 TCP/IP 概念。还有其他模型，比如 TCP/IP 模型：\n",
    "\n",
    "\n",
    "```livescript\n",
    "Application -> Transport 层 (TCP/UDP) -> IP 层 -> 链路层 (IP 以下)\n",
    "```\n",
    "\n",
    "TCP/IP 模型展示了协议头的结构，把 TCP 和 UDP 放在同一层，但 TCP 提供更高层功能，而 UDP 相当于 IP + 端口。\n",
    "\n",
    "还有 OSI 模型，但它比现实中的 TCP/IP 更复杂，所以忽略即可。\n",
    "\n",
    "**与我们真正相关的是什么**\n",
    " 典型应用不会直接接触 IP 层，因为复用是一个普遍需求。我们真正关心的只是 IP 层中的源地址和目的地址。\n",
    "\n",
    "以太网位于 IP 之下，也是基于包的，但使用不同类型的地址（MAC）。MAC 地址被一些不关心 IP 的硬件（比如某些交换机）使用。我们不关注这一层，在 VPN 中它甚至可能不存在。\n",
    "\n",
    "我们关心的是 IP 之上的层。应用要么直接使用 TCP 或 UDP 自定义协议，要么使用已有的成熟协议实现。我们会像真正的 Redis 那样，自行定义协议。\n",
    "\n",
    "TCP 和 UDP 都被 IP 所封装。IP 还可以封装其他协议，比如 SCTP，但到了 2025 年，只有 TCP 和 UDP 才是关键。所有东西都构建在 TCP 或 UDP 之上。\n",
    "\n",
    "结论：IP、端口、TCP/UDP 是我们要处理的核心概念。\n",
    "\n",
    "**请求-响应协议**\n",
    " Redis、HTTP/1.1 以及大多数 RPC 协议都是请求-响应协议。每个请求消息都有对应的响应消息。如果消息既不可靠也无序，就很难将响应和请求配对。因此，大多数请求-响应协议都基于 TCP（DNS 是个例外）。\n",
    "\n",
    "**数据包 vs. 字节流**\n",
    " TCP 提供的是字节流，但典型应用期望的是消息；很少有应用直接使用原始字节流而不解析。因此，我们要么在 TCP 之上添加消息层，要么在 UDP 上添加可靠性和顺序。前者轻松得多，所以大多数应用都使用 TCP，要么直接使用成熟协议，要么自定义协议。\n",
    "\n",
    "TCP 和 UDP 不仅功能不同，语义也不兼容。为网络应用选择 TCP 还是 UDP，是必须首先做出的决定。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2.3 Socket 原语\n",
    " 虽然我们是在 Linux 上编码，这些概念具有跨平台通用性。\n",
    "\n",
    "**什么是 socket？**\n",
    " Socket 是一个用于引用连接或其他对象的句柄。网络编程接口称为 socket API，在不同操作系统上大体类似。“socket”这个名字和墙上的插座没有任何关系。\n",
    "\n",
    "句柄是一种不透明的整数，用来引用跨越 API 边界的事物，正如 Twitter handle 引用某个用户。在 Linux 中，句柄被称为文件描述符（fd），它对进程是本地的。“文件描述符”只是一个名字，它既与文件无关，也不描述任何东西。\n",
    "\n",
    "`socket()` 方法会分配并返回一个 socket fd（句柄），以后会用它来真正创建连接。\n",
    "\n",
    "当你不再使用某个句柄时，必须关闭它，以释放操作系统端的相关资源。这是不同类型句柄的唯一共同点。\n",
    "\n",
    "**监听 socket 与连接 socket**\n",
    " 监听是告诉操作系统，某个应用准备好从指定端口接受 TCP 连接。操作系统随后返回一个 socket 句柄，用于让应用引用该端口。通过监听 socket，应用可以获取（accept）到来的 TCP 连接，这些连接也由 socket 句柄表示。因此，有两种句柄：监听 socket 与连接 socket。\n",
    "\n",
    "创建监听 socket 至少需要 3 个 API 调用：\n",
    "\n",
    "1. 通过 `socket()` 获取 socket 句柄。\n",
    "2. 通过 `bind()` 设置监听的 IP:端口。\n",
    "3. 通过 `listen()` 创建监听 socket。\n",
    "\n",
    "然后使用 `accept()` 等待到来的 TCP 连接。伪代码：\n",
    "\n",
    "```pseudo\n",
    "fd = socket()\n",
    "bind(fd, address)\n",
    "listen(fd)\n",
    "while True:\n",
    "    conn_fd = accept(fd)\n",
    "    do_something_with(conn_fd)\n",
    "    close(conn_fd)\n",
    "```\n",
    "\n",
    "**客户端发起连接**\n",
    " 客户端通过 2 个 API 调用创建一个连接 socket：\n",
    "\n",
    "1. 通过 `socket()` 获取 socket 句柄。\n",
    "2. 通过 `connect()` 创建连接。\n",
    "\n",
    "伪代码：\n",
    "\n",
    "```pseudo\n",
    "fd = socket()\n",
    "connect(fd, address)\n",
    "do_something_with(fd)\n",
    "close(fd)\n",
    "```\n",
    "\n",
    "`socket()` 创建的是无类型的 socket；它的类型（监听或连接）由后续的 `listen()` 或 `connect()` 调用决定。`socket()` 和 `listen()` 之间的 `bind()` 只是设置一个参数。`setsockopt()` 可以设置其他将于稍后使用的 socket 参数。\n",
    "\n",
    "**读与写**\n",
    " 虽然 TCP 和 UDP 提供的服务类型不同，但它们共享同一套 socket API，包括 `send()` 和 `recv()`。对于基于消息的 socket（UDP），每次 `send/recv` 对应一个数据包；对于基于字节流的 socket（TCP），每次 `send/recv` 都是对字节流的追加或消费。\n",
    "\n",
    "在 Linux 上，`send/recv` 只是通用读写系统调用 `read/write` 的一个变种，这些调用同时用于 socket、磁盘文件、管道等。不同类型的句柄共享相同的读写 API 只是巧合；几乎不会有代码既适用于 TCP 又适用于 UDP，因为它们语义不同。\n",
    "\n",
    "**总结：socket 原语列表**\n",
    "\n",
    " 监听 TCP socket：\n",
    "\n",
    "- `bind()` 与 `listen()`\n",
    "- `accept()`\n",
    "- `close()`\n",
    "\n",
    "使用 TCP socket：\n",
    "\n",
    "- `read()`\n",
    "- `write()`\n",
    "- `close()`\n",
    "\n",
    "创建 TCP 连接：\n",
    "\n",
    "- `connect()`\n",
    "\n",
    "下一章节将帮助你开始写实际的代码。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 03. TCP Server and Client"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "C++17",
   "language": "C++17",
   "name": "xcpp17"
  },
  "language_info": {
   "name": "C++17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
